大数据分析工程师入门5-HIVE基础
本文为《大数据分析师入门课程》系列的第5篇,主要讲解大数据分析师必须了解的Hive基础知识,前4篇分别是JAVA基础、SCALA基础、SQL基础和SQL进阶。
依照惯例,首先,我们就以下三个问题进行简单说明。
为什么讲Hive?
本文的主要目标是什么?
本文的讲解思路是什么?
为什么讲Hive
对于初级分析师来说,最日常的工作之一就是通过写SQL提取数据进行分析。
在大数据框架中,提供类SQL语言支持的就是Hive,简称为HiveQL。
首先,想要通过HiveQL进行数据提取和处理,前提是至少需要对HiveQL的基本语法、语句结构、数据类型、常用数据处理方法等掌握清楚,也就是至少要知道怎么用。
其次,要想用好Hive这个数据分析工具,就要对它有一个全面了解,才能提高工作效率和问题排查效率。
最后,Hive不仅作为数据分析工具,还普遍被用来进行数仓构建,学好Hive基础知识,有利于更深入地开展数据分析工作。
本文的主要目标是什么?
通过这篇文章,希望能够帮助稍微有一点点Hive或者SQL基础的读者快速了解Hive。
另外,对Hive曾进行过全面学习但大部分知识点稍有遗忘的读者,本文将带你一起回顾一下。
文章内容主要围绕作者认为数据分析工作中需要重点掌握的Hive基础知识进行讲解,每章节将围绕章节主题展开,章节内容中如有提及到读者以前没了解过的专业词汇,为避免讲解重心偏移,将不会进行专门的展开介绍,读者可自行百度补充了解一下。
本文的讲解思路是什么?
围绕数据分析工作中最常使用到的hive基础知识点进行展开,主要分为以下几个部分:
第1部分:主要介绍下Hive可作为数据分析工具和数仓构建工具。
第2部分:为什么要有Hive,主要是对于Hive的优势进行总结归纳。
第3部分:Hive支持的常用数据类型和文件格式。
第4部分:DDL,内外部表的区别,表的创建、修改、删除和数据的导入导出。
第5部分:常用Hive函数,包括函数语法、含义、示例等。
第6部分:如何添加UDF,将通过一个简单示例给出创建一个UDF的完整步骤,以及对每一步所需注意问题点进行说明。
第7部分:表关联,包括内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)、右外连接(RIGHT OUTER JOIN)、左半开连接(LEFT SEMI JOIN)以及这四大连接类型的效果示例。
话不多说,下面我们进入正式的知识讲解。
Hive的主要用途
作为数据统计分析工具
大数据框架中,Hive的存在为数据的查询、处理和分析提供了更为友好的操作接口。
因为HiveQL的绝大部分语法都是遵循SQL国际标准,非常简洁统一,书写方便,易学易用。只要有一点SQL基础的用户上手Hive的门槛将会很低,所以Hive的主要用途之一就是作为大数据日常工作中的数据分析工具。
作为数据仓库构建工具
Hive的另一个主要用途是作为数仓构建工具,与许多擅长结构化数据的传统 ETL 工具不同,创建 Hive是为了在 Hadoop 分布式文件系统(HDFS)中加载和转换非结构化、结构化或半结构化数据。
对于 ETL 而言,Hive 是一个强大的工具,它提供了一种将非结构化和半结构化数据转化为基于模式的可用数据的方法。
Hive的优势
Hive可以封装MapReduce、Tez等这些引擎的处理过程,让使用者在不了解这些计算引擎具体执行细节的情况下就可以处理数据,使用者只需要学会如何写sql即可。
Hive可很好的解决直接使用 MapReduce、Tez等时所面临的问题,使其具备了以下优势:
操作简单,学习成本低
直接使用 MapReduce、Tez学习成本太高,因为需要了解底层具体执行引擎的处理逻辑,而且需要一定的编码基础;而Hive提供直接使用类SQL语言即可进行数据查询和处理的平台或接口,只要使用者熟悉sql语言即可;
封装程度高,扩展性强
MapReduce、Tez实现复杂查询逻辑开发难度大,因为需要自己写代码实现整个处理逻辑以及完成对数据处理过程的优化,而Hive将很多数据统计逻辑封装成了可直接使用的函数,且支持自定义函数来进行扩展,而且Hive有逻辑和物理优化器,会对执行逻辑进行自动优化。
常用数据类型和文件格式
数据类型
Hive支持的数据类型分为两类,即基本数据类型和复杂数据类型。
基本数据类型
Hive数据类型 | Java数据类型 | 长度 | 示例 |
TINYINT | byte | 1byte有符号整数 | 20 |
SMALINT | short | 2byte有符号整数 | 20 |
INT | int | 4byte有符号整数 | 20 |
BIGINT | long | 8byte有符号整数 | 20 |
BOOLEAN | boolean | 布尔类型,true或者false | TRUE FALSE |
FLOAT | float | 单精度浮点数 | 3.14159 |
DOUBLE | double | 双精度浮点数 | 3.14159 |
STRING | string | 字符系列。可以指定字符集。可以使用单引号或者双引号。 | 'now is the time' "for all good men" |
TIMESTAMP | 时间类型 | '2019-04-23 03:02:12' | |
BINARY | 字节数组 | '2018-04-07' |
和其他的SQL语言一样,这些都是保留字。
需要注意的是所有的这些数据类型都是对Java中接口的实现,因此这些类型的具体行为细节和Java中对应的类型是完全一致的。例如,STRING类型实现的是Java中的String,float实现的是Java中的float,等等。hive中不区分大小写,所以Hive数据类型也可写成小写的形式。
复杂数据类型
数据类型 | 描述 | 示例 |
STRUCT | 和c语言中的struct类似,都可以通过“点”符号访问元素内容。例如,如果某个列的数据类型是STRUCT{first STRING, last STRING},那么第1个元素可以通过字段.first来引用。 | struct('John','Doe') |
MAP | MAP是一组键-值对元组集合,使用数组表示法可以访问数据。例如,如果某个列的数据类型是MAP,其中键->值对是'first'->'John'和'last'->'Doe',那么可以通过字段名['last']获取最后一个元素 | map('first','John','last','Doe') |
ARRAY | 数组是一组具有相同类型和名称的变量的集合。这些变量称为数组的元素,每个数组元素都有一个编号,编号从零开始。例如,数组值为['John', 'Doe'],那么第2个元素可以通过数组名[1]进行引用。 | Array('John','Doe') |
Hive 有三种复杂数据类型 ARRAY、MAP和STRUCT。
ARRAY和MAP与Java中的Array和 Map 类似,而STRUCT与C语言中的Struct类似,它封装了一个命名字段集合,复杂数据类型允许任意层次的嵌套,比如ARRAY里的单个元素依然是一个ARRAY。
Hive的文件格式
Hive可以支持多种文件格式,比如文本格式、sequence文件格式,甚至是用户自定义的文件格式。在介绍Hive常用的几种文件格式之前,先普及下行存储和列存储的概念。
首先我们来看以下这张表:
FieldA | FieldB | FieldC |
A1 | B1 | C1 |
A2 | B2 | C2 |
A3 | B3 | C3 |
行式存储
第一行 | 第二行 | 第三行 | ||||||
A1 | B1 | C1 | A2 | B2 | C2 | A3 | B3 | C3 |
列式存储
第一列 | 第二列 | 第三列 | ||||||
A1 | A2 | A3 | B1 | B2 | B3 | C1 | C2 | C3 |
行存储的特点:
查询满足条件的一整行数据的时候,行存储只需要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度更快。而且行存储这种存储格式可以比较方便进行INSERT和UPDATE操作。缺点是当查询只涉及某几个字段的时候,依然会读取所有数据。
列存储的特点:
因为每个字段的数据聚集存储,在查询只涉及少数几个字段的时候,能大大减少读取的数据量,但需要针对查询的几列字段进行重新组装;另外,因为每个字段的数据类型一定是相同的,因此列式存储可以有针对性地设计更好的压缩算法。这种存储格式的缺点是INSERT/UPDATE会比较麻烦。
我们了解了行式存储和列式存储的概念后,接下来一起来学习下Hive中常见的几种文件格式。
名称 | 特点 |
TEXTFILE | 行存储 1.Hive的默认文件格式,数据默认不做压缩,磁盘开销大,数据解析开销大。 2.可结合Gzip、Bzip2使用(系统自动检查,执行查询时自动解压),但使用Gzip时,Hive不会对数据进行切分,从而无法对数据进行并行操作。 |
SEQUENCEFILE | 1.SequenceFile是Hadoop API提供的一种二进制文件支持,其具有使用方便、可分割、可压缩的特点。 2.SequenceFile支持三种压缩选择:NONE,RECORD,BLOCK。RECORD压缩率低,一般建议使用BLOCK压缩。 |
RCFILE | 行列存储相结合的存储方式, 1.其将数据按行分块,保证同一个record在一个块上,避免读一个记录需要读取多个block。 2.块数据列式存储,有利于数据压缩和快速的列存取。 |
PARQUET | 列存储 1.可以跳过不符合条件的数据,只读取需要的数据,降低 IO 数据量。 2.压缩编码可以降低磁盘存储空间。 3.只读取需要的列,支持向量运算,能够获取更好的扫描性能。 4.Parquet 格式是 Spark SQL 的默认数据源,可通过 spark.sql.sources.default 配置。 |
创建本地测试表,格式分别为为TEXTFILE,SEQUENCEFILE,RCFILE和PARQUET。
create table if not exists textfile_table(
user_id string,
mac string,
md5_mac string)
row format delimited fields terminated by ' '
stored as textfile;
create table if not exists seqfile_table(
user_id string,
mac string,
md5_mac string)
stored as sequencefile;
create table if not exists rcfile_table(
user_idstring,
mac string,
md5_mac string)
stored as rcfile;
create table if not exists parquet_table(
user_idstring,
mac string,
md5_mac string)
stored as parquet;
设置压缩配置:
set hive.exec.compress.output=true;
set mapred.output.compress=true;
set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec;
set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec;
查看最终文件大小,相比TEXTFILE和SEQUENCEFILE,RCFILE和PARQUET虽然数据加载时性能消耗较大,但是具有较好的压缩比和查询响应。
数据仓库的特点是一次写入、多次读取,因此,整体来看,RCFILE和PARQUET相比其余两种格式具有较明显的优势。如果Hive底层应用的是SPARK引擎进行数据计算的话,那么数据最好存储为PARQUET,因为SPARK对PARQUET格式的数据读写提供了很好的数据读写性能支持和优化。
DDL&数据的导入导出
以上我们对Hive的常用数据类型和文件格式进行了讲解,接下来给大家讲解Hive的DDL,即对库与表的常见操作。
Hive的存储结构包括数据库、表、视图、分区和表数据等。数据库,表,分区等都对应HDFS上的一个目录。表数据对应 HDFS 对应目录下的文件。以下分为库和表两部分来简单总结一下常用的操作语法。
库的相关操作
(1)创建库
(2)查看库
(3)删除库
(4)切换库
表的相关操作
在介绍表的常见操作之前,我们先来了解下内部表、外部表、分区(partition)这几个在数据分析工作中经常会提到的概念和知识点。
首先来了解下内部表和外部表。
简单来说,未被external修饰的是内部表(managed table),被external修饰的为外部表(external table),以上讲解文件格式的时候,创建的就是内部表,如果加上external,那么创建的就是外部表,如下。
create external table if not exists textfile_table(
user_id string,
mac string,
md5_mac string)
row format delimited fields terminated by ' '
stored as textfile;
那么,什么情况下,创建内部表,什么情况下,创建外部表,它们之间的区别是什么呢?
那就是要看存入表中的数据是否需要与其他工作"共享",Hive对内部表控制着其数据的生命周期,当我们删除一个内部表时,Hive也会删除掉这个表中的数据。
而外部表,因为有可能存在其他工作也在共享这份数据,那么Hive认为其并非对这份数据完全拥有,所以在删除的时候,只会删除表,而不会删除数据。
所以,内外部表的概念更多的时候,就是为了表达某种意图,即当一份数据需要多方共享的时候,那么就创建为外部表更好。当然,通常情况下,为了避免不小心误操作,删除了表的时候把整个表的数据都删除了,那么,创建表的时候,最好还是创建为外部表最保险。
除了内部表、外部表,Hive中还有一个很重要的表概念,那就是分区表。
什么叫做分区表呢?
举一个例子,比如一家全国性的连锁企业,订单信息量很大,在进行存储时,我们可以考虑按照省份城市分开来存储。这样我们在查询某个特定城市的销售情况时,就不需要全表扫描读入,而是只读取其中一个分区的数据就可以了。
create table orders(
order_id string,
product string,
salesman string )
partitioned by (province string, city string)
stored as parquet;
分区表改变了Hive对数据存储的组织方式,不加分区的情况下,表的存储目录如下:
hdfs://user/hive/warehouse/test.db/orders
加上分区以后,Hive会创建反应分区结构的子目录,如下:
hdfs://user/hive/warehouse/test.db/orders/province=安徽/city=安庆
hdfs://user/hive/warehouse/test.db/orders/province=安徽/city=合肥
.......
市目录下存放具体的数据文件,这些文件中存放着订单信息。需要注意的是,因为分区字段已经反应在目录中了,所以文件中也就没有必要再存放这些字段了,查询的时候,对应数据也只会在对应目录下才能查到。
因此,分区表的优势就是能够减少读取数据量,极大提高查询速度(根据分区字段进行查询的时候)。
以上花了较大篇幅介绍了在Hive的表相关的操作中几个比较重要的基础概念。接下来我们一起来学习下在Hive中建表、如何添加字段、修改字段类型、删除字段、如何添加分区以及如何进行数据的导入和导出吧。
首先因为上文已经就如何创建内部表、外部表和分区表做了示例,以下将只针对另外两种建表方式给出示例。
建表
使用CTAS(create table ... as select...)创建表
//从一个查询SQL的结果来创建一个表进行存储
create table student_ctas as select * from studentwhere id < 95012;
复制表结构
create table student_copy like student;
接下来讲解添加字段、修改数据类型、删除字段
//添加字段
alter table new_student add columns(scores int);
//修改字段类型
alter table new_student change name new_name string;
//删除字段,hive中不支持直接删除一个字段,可通过replace全表来达到删除的目的
alter table new_student replace columns(id int,name string);
添加字段和修改字段类型的时候,都需要重新刷新分区(partition),因为通过上述命令改变的都是表的数据结构,而Hive针对每个分区都存储了一份数据结构,因此,如果想让已存在的分区使用最新的表结构,必须刷新分区。
Hive并没有提供刷新分区的命令,只能用先删除分区再添加的方式实现,具体的命令如下:
删除后再添加分区
alter table new_student drop partition (city='安徽');
alter table new_student add partition (city='安徽') location '/data_warehouse/new_student/city=安徽';
数据导入
1.创建表的时候直接指定路径
//将'/data/userdata'下的数据导入到新创建的外部表user中
create external table user(
id int,
name string)
row format delimited fields terminated by ' '
lines terminated by '\n' stored as textfile
localtion '/data/userdata';
2.创建表之后也可以导入数据到表中
(1)本机路径
load data local inpath '/data/data.txt' overwrite into table table_name partition(pt='time');
(2)Hadoop路径
load data inpath '/hadoop/data.txt' overwrite into table table_name partition(pt='time');
上面OVERWRITE关键字会全表覆盖,如果只是想附加数据,将OVERWRITE去掉即可,
(3)添加一个分区到表可以使用以下语句
//导入已有路径
alter table table_name add partition(pt='partition_name')location '/hive/pt=partition_name';
//将SQL查询结果导入分区
insert overwrite table table_name
partition(pt = 'partition_name')
select * from table_test where day = '20130620';
(4)还可以直接从其它的表拖数据过来
insert overwrite table table_name select id,name from table_name1;
数据导出
很多时候,我们在Hive中执行SELECT语句,希望将最终的结果保存到本地文件或者保存到HDFS系统中或者保存到一个新的表中,Hive提供了方便的关键词,来实现上面所述的功能。
1.将SELECT的结果放到一个新表中(首先要用create table创建新的表格)
insert overwrite table table_name2 select id,name from table_name1;
将从表table_name1中查询的数据插入到表table_name2中
2.将SELECT的结果放到本地文件系统中
insert overwrite local directory '/tmp/reg_3' row formatted delimited fields terminated by '\t' select * from table table_name;
在Hive环境下,将从表“table_name”中查询到的数据放在本地目录“/tmp/reg_3”下
3.将SELECT的结果放到HDFS文件系统中
insert overwrite directory '/tmp/hdfs_out' select a.* from table_name where a.ds='<date>';
将从表table_name中查询到的数据放在HDFS的'/tmp/hdfs_out'目录下
4.通过Hadoop命令将表中的数据导入到本地文件目录下
hadoop fs -get '/hive/t1' /data/
将表目录hive下的表t1中的数据导入到本地目录/data/下
常用函数列表
文章开篇第二段曾讲过,Hive所发挥出来的其中一个最主要的优势就是Hive将很多数据统计逻辑封装成了可直接使用的函数,且支持自定义函数来进行扩展。本文将所有常用的函数通过几张表格罗列在下面。
分类 | 函数 | 语法 | 适用类型 |
关系函数 | 等值比较:= | A=B | 全部基本类型 |
不等值比较:<> | A<>B | 全部基本类型 | |
小于比较:< | A<B | 全部基本类型 | |
空值推断:IS NULL | a is null | 全部基本类型 | |
非空推断:IS NOT NULL | a is not null | 全部基本类型 | |
LIKE比较:LIKE | A LIKE B | string | |
正则LIKE操作:RLIKE | A RLIKE B 注:假设字符串A或者字符串B为NULL。则返回NULL;假设字符串符合JAVA正则表达式B的正则语法,则为TRUE,否则为FALSE。 | string | |
REGEXP操作:REGEXP | A REGEXP B 注:功能与RLIKE同样 | string | |
数学运算 | 加法操作:+ | A+B | 全部数值类型 |
减法操作:- | A-B | 全部数值类型 | |
取余操作:% | A%B | 全部数值类型 | |
位与操作:& | A&B | 全部数值类型 | |
位或操作:| | A|B | 全部数值类型 | |
位异或操作:^ | A^B | 全部数值类型 | |
位取反操作:~ | ~A | 全部数值类型 |
分类 | 函数 | 语法 | 返回类型或返回值 |
逻辑运算 | 逻辑与操作:AND | A AND B | boolean |
逻辑或操作:OR | A OR B | boolean | |
逻辑非操作:NOT | NOT A | boolean | |
数值计算 | 取整函数:round | round(3.14) | 3 注:返回double类型的整数值部分(遵循四舍五入) |
向下取整函数:floor | floor(3.56) | 3 注:返回等于或者小于该double变量的最大的整数 | |
向上取整函数:ceil | ceil(3.123) | 4 注:返回等于或者大于该double变量的最小的整数 | |
取随机数函数:rand | rand() | 返回一个0到1范围内的随机数。 | |
自然指数函数exp | exp(2) | 7.38905609893065 注:返回自然对数e的a次方 | |
以10为底对数函数:log10 | log10(100) | 2 注:返回数据类型为BIGINT | |
幂运算函数:pow/power | pow(a,b) | 返回a的b次方 | |
开平方函数:sqrt | sqrt(16) | 4 注:参数开平方 | |
绝对值函数:abs | conv(-3.3) | 3.3 注:返回参数的绝对值 | |
字符串函数 | 字符串长度函数:length | length('acbfa') | 5 注:返回字符串的长度 |
字符串反转函数:reverse | reverse('abcde') | edcba 注:字符串反转 | |
字符串链接函数:concat | concat('aa','bb','cc') | aabbcc 注:将多个字符串拼接为一个字符串 | |
带分隔符字符串连接函数:concat_ws | concat_ws(',','aaa','bbb','ccc') | aaa,bbb,ccc 将多个字符串按分隔符拼接起来 | |
字符串截取函数:substr,substring | substr('abcde',3) substr('abcde',-1) substr('abcde',-2,2) substr('abcde',3,2) | cde e de cd 注:截取字符串的指定部分生成新的字符串 | |
字符串转大写函数:upper,ucase | upper('abc') | 'ABC' 将字符串中的所有字符转为大写 | |
字符串转小写函数:lower,lcase | lower('ABC') | 'abc' 将字符串中的所有字符转为小写 | |
去空格函数:trim()、ltrim()、rtrim() | trim(' hgd ') ltrim(' hgd ') rtrim(' hgd ') | 'hgd' 'hgd ' ' hgd' 注:去除字符串两边或左边或右边的空格 | |
正則表達式替换函数:regexp_replace | regexp_replace('foobar','oo|ar','') | fb 注:替换字符串的指定部分 | |
切割字符串函数:split | split('abtcdtef','t') | ["ab","cd","ef"] 注:按t分割,返回字符串数组 | |
集合查找函数:find_in_set | find_in_set('ab','ef,ab,de') | 2 注:返回str在strlist第一次出现的位置,strlist是用逗号分割的字符串。如果没有找该str字符,则返回0 | |
转换函数 | 类型转换函数:cast | cast(1 as float) | 1.0 注:将1转换成float类型,变成1.0 |
日期函数 | to_date | to_date('2019-04-02 02:23:31') | '2019-04-02' 注:从一个字符串中取出为日期的部分 |
year、month、day | year('2019-04-02 02:23:31') month('2019-04-02 02:23:31') day('2019-04-02 02:23:31') | 2019 4 2 注:从一个日期中取出相应的年、月、日 | |
weekofyear | weekofyear('2018-06-28 18:02:53') | 26 注:返回输入日期在该年中是第几个星期 | |
datediff | datediff('2018-06-28 18:02:53','2017-06-28 18:02:53') | 365 注:两个日期相减,返回相差天数 | |
date_add | date_add('2018-06-28 18:02:53',2) | 2018-06-30 注:在一个日期基础上增加天数 | |
date_sub | date_sub('2018-06-28 18:02:53',2) | 2018-06-26 注:在一个日期基础上减去天数 | |
聚合函数 | count | count(*),count(1),count(column) | 返回结果记录数的总条数, count(*):所有行进行统计,包括NULL行 count(1):NULL值不进行统计 count(column):对column中非NULL进行统计 |
sum | sum(column) | 将某列进行累加(如果某行的列的为NULL则忽略) 注:字段类型如果是int当然没问题,如果是string类型但是全部是数字也没问题,如果包含一个字母,sum出来将会是0 | |
max | max(column) | 针对数值型和日期型的列返回该列的最大值,如果对字符串列进行max(),效果是将会对相邻两行的该字段值依次从左至右挨个进行大小比较,最后得到最大值,故max操作对字符串等的应用是没有意义的。
| |
min | min(column) | 针对数值型和日期型的列返回该列的最小值,如果对字符串列进行min(),道理跟max是一样的 | |
avg | avg(column) | 针对数据型进行的计算,其他字段类型使用avg都没有实际意义。 |
UDF( user defined function)
Hive不仅有很多可直接使用的函数(见上一段内容),还支持用户自定义函数。这样用户就可以结合各种业务场景自己构建UDF(用户自定义函数)以供HiveQL直接调用。
一旦将用户自定义函数加入到用户会话中,它们就将和内置函数一样使用。
Hive自定义临时通用函数的步骤如下,以一个简单示例我们一起来操作一下:
1. 在类中创建自定义函数。
自定义UDF需要继承org.apache.hadoop.hive.ql.exec.UDF,实现evaluate函数,evaluate函数支持重载。
package com.yqz.udf;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;
public class ConcatString extends UDF {
// string can not translation inhadoop
public Text evaluate(Text a, Text b){
return newText(a.toString() + "*******" + b.toString());
}
}
查询执行过程中,查询中对应的每个应用到这个函数的地方都会对这个类进行实例化。对于每行输入都会调用evaluate()函数。
2.将该类所在的包导出成jar包,放入linux目录下。
//先删除旧包
delete jar /data/yqz/hive/contactString.jar;
//添加新的jar包
add jar /data/yqz/hive/contactString.jar;
需要注意的是,jar文件路径是不需要用引号括起来的。这个路径需要是当前文件系统的全路径。Hive不仅仅将这个jar文件放入到classpath中,同时还将其加入到分布式缓存中,这样,整个集群的机器都是可以获得该jar文件的。
3.创建临时函数,指向jar包中的类。
//语法:create temporary function <函数名> as 'java类名';
//示例
create temporary function myconcat as'com.yqz.udf.ConcatString';
需要注意的是,create temporary function中的temporary 关键字表示的是当前会话中声明的函数只会在当前会话中有效。
因此用户需要在每个会话中都添加jar,然后再创建函数。如果用户需要长期频繁的使用同一个jar和函数的话,可以将相关语句增加到$HOME/.hiverc文件中去。
4.使用临时函数
//语法:
使用:select <函数名> (参数);
删除:drop temporary function if exists <函数名>;
//示例
select myconcat('HELLO','world');
删除自定义临时函数时,加上if exists,这样即使该函数不存在,也不会报错。
表关联
Hive支持通常的SQL JOIN语句,但只支持等值连接。
本文将介绍(内连接)INNER JOIN、(左外连接)LEFT OUTER JOIN、(右外连接)RIGHT JOIN、(全连接)FULL OUTER JOIN、(左半开连接)LEFT SEMI JOIN。
表关联的知识点并不复杂,但是想用好表关联,需要大量的实战练习,大家可以在工作中通过各种各样的案例,来加强这块知识的学习,我们在本课程的最后两篇文章,也会给大家提供可以练习的数据和需求案例。
为方便讲解,首先我们创建接下来用于示例的基础表。
基础表数据如下
建表
create table a(
id int,
name string)
row format delimited
fields terminated by ',';
create table b(
id int,
name string)
row format delimited
fields terminated by ',';
load data local inpath '/home/test/a.txt' intotable a;
load data local inpath '/home/test/b.txt' intotable b;
内连接(INNER JOIN)
内连接通过INNER JOIN关键字标识,实际使用中,INNER可省略不写,它的含义是左右两个集合相乘后,只保留满足ON后面关联条件的记录。所以,可以利用内连接计算两个集合的交集,只需要把集合元素的字段都写在ON后面的关联条件里即可。
示例:
//a表和b表通过id字段关联
select * from a inner join b on a.id=b.id;
//结果
+-------+---------+-------+---------+--+
| a.id | a.name | b.id |b.name |
+-------+---------+-------+---------+--+
| 2 | b | 2 | bb |
| 3 | c | 3 | cc |
| 7 | y | 7 | yy |
+-------+---------+-------+---------+--+
左外连接(LEFT OUTER JOIN)
左外连接通过LEFT OUTER关键字标识,实际使用中,OUTER可省略不写。
它的含义是左右两个集合相乘后,LEFT OUTER JOIN操作符左边表中符合WHERE条件的所有记录将被返回,右边表中通过ON条件关联上的部分将返回右边表中指定选择列的值,其余未关联上的部分将通过给右边表的指定返回列赋以NULL值返回。
如果右边表全都与左边表没匹配上,那右边表指定选择列在结果集中将全是NULL值。
示例:
//a表和b表通过id字段进行左外连接
select * from a left join b on a.id=b.id;
//结果
+-------+---------+-------+---------+--+
| a.id | a.name | b.id |b.name |
+-------+---------+-------+---------+--+
| 1 | a | NULL | NULL |
| 2 | b | 2 | bb |
| 3 | c | 3 | cc |
| 4 | d | NULL | NULL |
| 7 | y | 7 | yy |
| 8 | u | NULL | NULL |
+-------+---------+-------+---------+--+
右外连接(RIGHT OUTER JOIN)
右外连接与左外连接含义相同,区别在于左外连接是以左边表为主,保留左边表的所有记录,右外连接是以右边表为主,保留右边所有记录。
其实在实际工作中,只要把想保留所有记录的表一直写在左边,那么右外连接基本就用不上了。
示例:
//a表和b表通过id字段进行右外连接
select * from a right join b on a.id=b.id;
//结果
+-------+---------+-------+---------+--+
| a.id | a.name | b.id |b.name |
+-------+---------+-------+---------+--+
| 2 | b | 2 | bb |
| 3 | c | 3 | cc |
| 7 | y | 7 | yy |
| NULL | NULL | 9 | pp |
+-------+---------+-------+---------+--+
全连接(FULL OUTER JOIN)
全连接含义是,左右两个集合相乘后,保留满足ON后面关联条件的记录加上左表和右表中原有的但未关联成功的记录。
示例:
//a表和b表通过id字段进行全连接
select * from a full outer join b on a.id=b.id;
//结果
+-------+---------+-------+---------+--+
| a.id | a.name | b.id |b.name |
+-------+---------+-------+---------+--+
| 1 | a | NULL | NULL |
| 2 | b | 2 | bb |
| 3 | c | 3 | cc |
| 4 | d | NULL | NULL |
| 7 | y | 7 | yy |
| 8 | u | NULL | NULL |
| NULL | NULL | 9 | pp |
+-------+---------+-------+---------+--+
左半开连接(LEFT SEMI JOIN)
因为Hive中没有类似标准SQL中的in/exists之类的查询,所以需要有替代性的操作来完成类似功能,左半开连接(LEFTSEMI JOIN)就是起到这样的作用。
比如,在SQL中,想要查询a表中id存在于b表中的a表记录,SQL中查询语句如下:
select * from a where id in (select id from b whereb.name <> 'y')
//返回结果
+-------+---------+--+
| a.id | a.name |
+-------+---------+--+
| 2 | b |
| 3 | c |
+-------+---------+--+
在hive中通过左半开连接来完成该操作,前提要求LEFT SEMI JOIN操作符右边的表满足ON语句中的判定条件。
select * from a left semi join b on a.id=b.id and b.name<>'y';
//返回结果
+-------+---------+--+
| a.id | a.name |
+-------+---------+--+
| 2 | b |
| 3 | c |
+-------+---------+--+
关于左半开连接(LEFT SEMI JOIN),有以下几项特性总结:
1.返回的结果是LEFT SEMI JOIN操作符左边表中的指定选项记录。
2.LEFT SEMI JOIN 的限制是,JOIN 子句中右边的表只能在 ON 子句中设置过滤条件,在WHERE 子句、SELECT 子句或其他地方过滤都不行。
3.对于左边表中一条指定的记录,在右边表中一旦找到匹配的记录,Hive就会立即停止扫描。
总结
本文将数据分析工作中可能会涉及到的Hive基础知识提炼出来跟大家一起学习和回顾了一下,主要包括Hive的主要用途及优势、Hive常用数据类型和文件格式、Hive的DDL与数据导入导出、Hive的常用函数列表、Hive的UDF和表关联相关的知识,希望对大家所有帮助。
-end-
参考文献
1.《hive编程指南》 Edward Capriolo Dean Wampler Jason Rutherglen著
2.hive官网 http://hive.apache.org/
3.https://www.cnblogs.com/sunfie/p/4373930.html 天~宇~翱~翔
相关阅读: